/*
TinyXmlStd http://code.google.com/p/tinyxmlstd/
based on TinyXml, www.sourceforge.net/projects/tinyxml
Original code (2.0 and earlier )copyright (c) 2000-2006 Lee Thomason (www.grinninglizard.com)

This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.

Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.

2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.

3. This notice may not be removed or altered from any source
distribution.
*/

/*
force all objects to be non-copyable?
add proper unit testing and use the old test cases
add several different iterators!
clean up parser
 - remove char*
 - what does stamp() do?
 - support streaming files
move out nodelist to a childnode container class
*/

#ifndef TINYXMLSTD_INCLUDED
#define TINYXMLSTD_INCLUDED

#include <string>
#include <iostream>
#include <sstream>
#include <vector>
#include <map>

#include <boost/shared_ptr.hpp>
#include <boost/iterator/iterator_facade.hpp>
#include <boost/noncopyable.hpp>

#include "tinyxmlstd-unicode.h"

#if defined( _DEBUG ) && !defined( DEBUG )
#define DEBUG
#endif

#ifdef TINYXMLSTD_UNITTEST
#define XmlAssert(x) if(x) ; else throw #x
#else
#define XmlAssert(x) assert(x)
#endif

namespace tinyxmlstd
{
	class Document;
	class Element;
	class Comment;
	class Unknown;
	class Attribute;
	class Text;
	class Declaration;
	class ParsingData;
	class Node;
	class ErrorStore;

	typedef boost::shared_ptr<Attribute> AttributePtr;
	typedef std::map<String, AttributePtr> AttributeMap;
	typedef std::vector<AttributePtr> AttributeList;

	typedef boost::shared_ptr<Node> NodePtr;
	typedef std::vector<NodePtr> NodeList;

	typedef boost::shared_ptr<Document> DocumentPtr;
	typedef boost::shared_ptr<Element> ElementPtr;
	typedef boost::shared_ptr<Comment> CommentPtr;
	typedef boost::shared_ptr<Unknown> UnknownPtr;
	typedef boost::shared_ptr<Text> TextPtr;
	typedef boost::shared_ptr<Declaration> DeclarationPtr;

	const int kMajorVersion = 2;
	const int kMinorVersion = 5;
	const int kPatchVersion = 3;

	// Setting the tabsize to 0 disables row/column tracking.
	struct FileLocation
	{
		FileLocation();
		FileLocation(int r, int c);

		void clear();

		int row; // 0 based.
		int character; // 0 based.

		bool hasRow() const;
		bool hasCharacter() const;

		bool hasBothRowAndCharacter() const;
	};

	namespace VisitorAction
	{
		enum Type
		{
			  Continue
			, Abort
		};
	}

	class Visitor
	{
	public:
		virtual ~Visitor();

		virtual VisitorAction::Type visitEnter( const Document& doc );
		virtual VisitorAction::Type visitExit( const Document& doc );

		virtual VisitorAction::Type visitEnter( const Element& element);
		virtual VisitorAction::Type visitExit( const Element& element);

		virtual VisitorAction::Type visit( const Declaration& declaration );
		virtual VisitorAction::Type visit( const Text& text );
		virtual VisitorAction::Type visit( const Comment& comment );
		virtual VisitorAction::Type visit( const Unknown& unknown );

		VisitorAction::Type doAccept(Node& node);
	};

	namespace QueryResult
	{
		enum Type
		{ 
			Success,
			NoAttribute,
			WrongType
		};
	}

	namespace Encoding
	{
		enum Type
		{
			Unknown,
			Utf8,
			Legacy
		};
	}

	namespace NodeType
	{
		enum Type
		{
			Document,
			Element,
			Comment,
			Unknown,
			Text,
			Declaration,
			NumberOfNodeTypes
		};
	}

	namespace WhitespaceAction
	{
		enum Type
		{
			  Condense
			, Keep
		};
	}

	const Encoding::Type kDefaultEncoding = Encoding::Unknown;

	namespace Error
	{
		enum Type
		{
			None = 0,
			Error,
			OpeningFile,
			ParsingElement,
			FailedToReadElementName,
			FailedReadingElementValue,
			FailedReadingElementAttributes,
			FailedParsingEmpty,
			FailedReadingEndTag,
			FailedParsingUnknown,
			FailedParsingComment,
			FailedParsingDeclaration,
			DocumentEmpty,
			EmbeddedNull,
			ParsingCdata,
			DocumentTopOnly,

			NumberOfErrorCodes
		};
	}

	class OutFile : boost::noncopyable
	{
	public:
		virtual ~OutFile();
		virtual void print(const String& str) = 0;
		virtual void print(const Char c) = 0;
	};

	class OutFileStream : public OutFile
	{
	public:
		OutFileStream(Ostream& o);
		void print(const String& str);
		void print(const Char c);
	private:
		Ostream& os;
	};

	class InFile
	{
	public:
		InFile(const Char* data, std::size_t length);

		const std::size_t length() const;
		const Char* const getDataPointer() const;
	private:
		const Char* mData;
		std::size_t mLength;
	};

	class InFileReader
	{
	public:
		// read in binary mode so that tinyxml can normalize the EOL
		InFileReader(Istream& f);
		InFileReader(const Ostringstream& f);
		InFileReader(const String& source);

		InFile data() const;

		operator InFile() const;
	private:
		String mData;
	};

	class Base : boost::noncopyable
	{
	protected:
		Base();

	public:
		const FileLocation& getFileLocation() const;
		void setFileLocation(const FileLocation& fl);

		void setUserData( void* user );
		void* getUserData();
		const void* getUserData() const;
	private:
		FileLocation mLocation;
		void* mUserData;
	};

	bool GetSpecialChar_fromChar(Char toFind, String* result);
	bool GetSpecialChar_fromEncoded(const Char* toFind, Char* result, std::size_t* length);

	class ChildList;

	class ElementsIterator;
	class ElementsNamedIterator;

	class ChildList
	{
	public:
		ChildList();
		void internal_postConstructor(Node* parent);

		void add(NodePtr node);
		//void remove(NodePtr removeThis);
		const NodeList& list() const;
		void accept(Visitor* visitor) const;
		bool empty() const;
		void clear();

		ElementsIterator elements_begin();
		ElementsIterator elements_end();

		ElementsNamedIterator elementsNamed_begin(const String& name);
		ElementsNamedIterator elementsNamed_end(const String& name);
	private:
		Node* mParent;
		NodeList mNodes;
	};

	std::pair<ElementsIterator, ElementsIterator> ElementsIn(ChildList& list);
	std::pair<ElementsNamedIterator, ElementsNamedIterator> ElementsNamedIn(const String& name, ChildList& list);

	NodePtr GetFirstChild(const ChildList& node);
	TextPtr GetFirstText(const ChildList& node);
	ElementPtr GetFirstChildElement(const ChildList& node);
	ElementPtr GetFirstChildElementNamed(const String& name, const ChildList& node);

	class Node : public Base
	{
	public:
		virtual ~Node();

		/// One step up the DOM.
		Node* getParent();
		const Node* getParent() const;
		void setParent(Node* node);

		NodeType::Type getType() const;

		virtual VisitorAction::Type accept(Visitor* visitor) const = 0;

		const ChildList& getChildren() const;
		ChildList& getChildren();
	protected:
		Node( NodeType::Type type );
	private:
		Node* mParent;
		NodeType::Type mType;
		ChildList mNodes;
	};

	class Attribute : public Base
	{
	public:
		Attribute();
		Attribute( const String& name, const String& value );

		const String& getName() const;
		const String& getValue() const;

		void setName( const String& name );
		void setValue( const String& value );

		bool operator==( const Attribute& rhs ) const;
		bool operator<( const Attribute& rhs ) const;
		bool operator>( const Attribute& rhs ) const;
	private:
		String mName;
		String mValue;
	};

	class AttributeSet : boost::noncopyable
	{
	public:
		AttributeSet();
		~AttributeSet();

		void clear();
		void set(const String& name, const String& value);
		const String& get(const String& name) const;

		QueryResult::Type queryIntValue(const String& name, int* ival ) const;
		QueryResult::Type queryDoubleValue(const String& name, double* dval ) const;
		QueryResult::Type AttributeSet::query(const String& name, String* sval ) const;
		void set(const String& name, int value);
		void set(const String& name, double value );
		int getIntValue(const String& name) const;
		double getDoubleValue(const String& name) const;

		void add( AttributePtr attribute );
		void remove(const String& name);

		const AttributeMap& map() const;
		const AttributeList& list() const;

		const AttributePtr find( const String& name ) const;
		AttributePtr find( const String& name );
	private:
		AttributeMap mAttributeMap;
		AttributeList mAttributeList;
	};

	class Element : public Node
	{
	public:
		Element(const String& name);

		virtual ~Element();
		
		//const String& getChildText() const;

		const String& getName() const;
		void setName(const String& name);

		virtual VisitorAction::Type accept(Visitor* visitor) const;

		AttributeSet& getAttributes();
		const AttributeSet& getAttributes() const;

		static ElementPtr Cast(NodePtr node);
	private:
		AttributeSet mAttributeSet;
		String mName;
	};

	class ElementsIterator
		: public boost::iterator_facade<ElementsIterator, Element, boost::forward_traversal_tag>
	{
	public:
		ElementsIterator();
		ElementsIterator(ChildList* list, NodeList::iterator it);

	private:
		friend class boost::iterator_core_access;

		void increment();
		bool equal(ElementsIterator const& other) const;
		Element& dereference() const;
	private:
		ChildList* list;
		NodeList::iterator iter;
	};

	class ElementsNamedIterator
		: public boost::iterator_facade<ElementsNamedIterator, Element, boost::forward_traversal_tag>
	{
	public:
		ElementsNamedIterator();
		ElementsNamedIterator(ChildList* list, NodeList::iterator it, const String& name);

	private:
		friend class boost::iterator_core_access;

		void increment();
		bool equal(ElementsNamedIterator const& other) const;
		Element& dereference() const;
	private:
		ChildList* list;
		NodeList::iterator iter;
		String name;
	};

	class Comment : public Node
	{
	public:
		Comment();
		Comment( const String& value );

		virtual ~Comment();

		virtual VisitorAction::Type accept( Visitor* visitor ) const;

		const String& getValue() const;
		void setValue(const String& val);

		static CommentPtr Cast(NodePtr node);
	private:
		String mValue;
	};


	// both standard text and cdata
	class Text : public Node
	{
	public:
		Text(const String& initValue); // non cdata
		virtual ~Text();

		bool isCdata() const;
		void setCdata( bool cdata );

		virtual VisitorAction::Type accept( Visitor* content ) const;

		const String& getValue() const;
		void setValue(const String& val);
		bool isOnlyWhitespace() const;

		static TextPtr Cast(NodePtr node);
	private:
		bool mIsCdata;
		String mValue;
	};

	// <?xml version="1.0" standalone="yes"?>
	class Declaration : public Node
	{
	public:
		Declaration();
		Declaration( const String& version,
			const String& encoding,
			const String& standalone );

		virtual ~Declaration();

		const String& getVersion() const;
		const String& getEncoding() const;
		const String& getStandalone() const;

		const void setVersion(const String& val);
		const void setEncoding(const String& val);
		const void setStandalone(const String& val);

		virtual VisitorAction::Type accept( Visitor* visitor ) const;

		static DeclarationPtr Cast(NodePtr node);
	private:
		String mVersion;
		String mEncoding;
		String mStandalone;
	};

	class Unknown : public Node
	{
	public:
		Unknown();
		virtual ~Unknown();

		virtual VisitorAction::Type accept( Visitor* content ) const;

		const String& getCode() const; // the content
		void setCode(const String& code);

		static UnknownPtr Cast(NodePtr node);
	private:
		String mCode;
	};

	class ErrorStore
	{
	public:
		ErrorStore();

		bool hasError() const;
		const String& getErrorDescription() const; // Contains a textual (english) description of the error if one occurs.
		Error::Type getErrorId() const;
		int getErrorRow() const;
		int getErrorChar() const;
		void clearError();

		void setError( Error::Type err, const Char* errorLocation, ParsingData* prevData, Encoding::Type encoding );
	private:
		Error::Type mErrorId;
		FileLocation mErrorLocation;
	};

	class Document : public Node
	{
	public:
		Document();
		virtual ~Document();

		bool load(const String& source, Encoding::Type encoding = kDefaultEncoding);
		bool load(const InFile&, Encoding::Type encoding = kDefaultEncoding);

		void save(OutFile&) const;

		const ElementPtr getRootElement() const;
		ElementPtr getRootElement();

		void setTabSize( int tabsize );
		int getTabSize() const;

		virtual VisitorAction::Type accept( Visitor* content ) const;

		const ErrorStore& getError() const;

		void setMicrosoftBom(bool bom);
		bool getMicrosoftBom() const;

		bool calculateFileLocation() const;
		void calculateFileLocation(bool cfl);

		static DocumentPtr Cast(NodePtr node);
	private:
		ErrorStore mError;
		int mTabsize;
		bool mUseMicrosoftBOM; // the UTF-8 BOM were found when read. Note this, and try to write.
		bool mCalculateFileLocation;
	};

	bool LoadFile(Document* doc, const String& file);

	class Handle
	{
	public:
		Handle( NodePtr node );
		Handle( const Handle& ref );
		Handle operator=( const Handle& ref );

		Handle getFirstChild() const;
		Handle getFirstChildElement() const;

		/*Handle getChild( int index ) const;
		Handle getChildElement( int index ) const;*/

		NodePtr asNode() const;
		ElementPtr asElement() const;
		TextPtr asText() const;
		UnknownPtr asUnknown() const;
	private:
		NodePtr mNode;
	};

	class Printer : public Visitor, boost::noncopyable
	{
	public:
		Printer(OutFile& file);

		virtual VisitorAction::Type visitEnter(const Document& doc);
		virtual VisitorAction::Type visitExit(const Document& doc);

		virtual VisitorAction::Type visitEnter(const Element& element);
		virtual VisitorAction::Type visitExit(const Element& element);

		virtual VisitorAction::Type visit(const Declaration& declaration);
		virtual VisitorAction::Type visit(const Text& text);
		virtual VisitorAction::Type visit(const Comment& comment);
		virtual VisitorAction::Type visit(const Unknown& unknown);

		Printer& setIndent( const String& indent );
		const String& getIndent();

		Printer& setLineBreak( const String& lineBreak );
		const String& getLineBreak();

		Printer& setDenseFormat();
	private:
		void appendIndent();
		void appendLineBreak();

		int mDepth;
		bool mSimpleTextPrint;
		String mIndent;
		String mLineBreak;
		OutFile& mFile;
	};
}

#endif